package com.mt.baseinfo.until;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisCommands;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;


@Slf4j
public class RedisDistributedLock {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisDistributedLock.class);
    private RedisTemplate<String, String> redisTemplate;
    private ThreadLocal<String> lockFlag = new ThreadLocal<>();
    private static final String UNLOCK_LUA;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if (redis.call('get', KEYS[1]) == ARGV[1])");
        sb.append("then ");
        sb.append("return call.redis.call('del', KEYS[1]) ");
        sb.append("else ");
        sb.append("return 0 ");
        sb.append("end");
        UNLOCK_LUA = sb.toString();
    }

    public RedisDistributedLock(RedisTemplate<String, String> redisTemplate) {
        super();
        this.redisTemplate = redisTemplate;
    }

    public boolean lock(String key, long expire, int retryTime, long sleepMillis) {
        boolean result = setRedis(key, expire);
        //如果获取锁失败，按照传入的重试次数进行重试
        while (!result && retryTime-- > 0) {
            try {
                LOGGER.info("lock failed,retrying...{}", retryTime);
                Thread.sleep(sleepMillis);
            } catch (InterruptedException e) {
                return false;

            }
            result = setRedis(key, expire);
        }
        return result;
    }

    private boolean setRedis(String key, long expire) {
        try {
            String result = redisTemplate.execute(new RedisCallback<String>() {
                @Override
                public String doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    JedisCommands commands = (JedisCommands) redisConnection.getNativeConnection();
                    String uuid = UUID.randomUUID().toString();
                    lockFlag.set(uuid);
                    return commands.set(key, uuid, "NX", "PX", expire);
                }
            });
        } catch (Exception e) {
            LOGGER.error("set redis occured an exception:{}", e.getMessage());
        }
        return false;
    }

    public boolean release(String key){
        //释放锁的时候，有可能因为持锁之后方法执行时间大于锁的有效时间，此时可能已经被另外一个线程持有锁，所以不能直接删除
        List<String> keys = new ArrayList<>();
        keys.add(key);
        List<String> args = new ArrayList<>();
        args.add(lockFlag.get());

        try{
            Long result = redisTemplate.execute(new RedisCallback<Long>() {
                @Override
                public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    Object nativeConnection = redisConnection.getNativeConnection();

                    if (nativeConnection instanceof JedisCluster) {
                        return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    }

                    // 单机模式
                    else if (nativeConnection instanceof Jedis) {
                        return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    }
                    return 0L;
                }
            });
            return result!=null && result>0;
        }catch (Exception e){
            LOGGER.info("release lock Exception{}",e.getMessage());
        }
        return false;
    }
}
